package com.autonavi.yunda.yunji.core.utils;

import com.autonavi.yunda.yunji.common.exception.AmapException;
import com.autonavi.yunda.yunji.common.exception.AmapExceptionCode;
import com.autonavi.yunda.yunji.common.utils.JsonUtils;
import com.autonavi.yunda.yunji.core.engine.exception.EngineException;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.hssf.usermodel.HSSFRichTextString;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFRichTextString;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import static com.autonavi.yunda.yunji.core.utils.FileUtil.createTempDirectory;

/**
 * 文件导出
 */
public class ExportUtil {
    public static void write(HttpServletResponse response, Object output, String exportType) {
        write(response, output, exportType, "output");
    }

    public static void write(HttpServletResponse response, Object output, String exportType, String fileName) {
        try (OutputStream out = response.getOutputStream()) {
            response.addHeader("Access-Control-Expose-Headers", "Content-Disposition");
            if ("csv".equals(exportType)) {
                response.setContentType("application/msexcel;charset=UTF-8");
                response.addHeader("Content-Disposition", String.format("attachment; filename=%s.csv", fileName));
                writeCsvContent(out, output);
            } else if ("xls".equals(exportType) || "xlsx".equals(exportType)) {
                response.setContentType("application/octet-stream;charset=UTF-8");
                response.addHeader("Content-Disposition", String.format("attachment; filename=%s.%s", fileName, exportType));
                writeExcelContent(out, output, exportType);
            } else if ("txt".equals(exportType)) {
                response.addHeader("Content-Disposition", String.format("attachment; filename=%s.txt", fileName));
                out.write(JsonUtils.toString(output).getBytes(StandardCharsets.UTF_8));
            } else {
                throw new EngineException(AmapExceptionCode.COMMON_ILLEGAL_PARAM.getCode() + "", "暂不支持改导出类型 :" + exportType);
            }
        } catch (IOException e) {
            throw new AmapException(AmapExceptionCode.COMMON_SERVER_ERROR, "导出文件异常", e);
        }
    }

    /**
     * 导出csv内容
     *
     * @param output 返回结果
     */
    public static void writeCsvContent(OutputStream outputStream, Object output) throws IOException {
        StringBuilder sb = new StringBuilder();
        if (output instanceof List) {
            List<Map<String, Object>> list = (List<Map<String, Object>>) output;
            for (int i = 0; i < list.size(); i++) {
                Set<Map.Entry<String, Object>> lineSet = list.get(i).entrySet();
                // 字段名
                if (i == 0) {
                    String[] titles = lineSet.stream().map(Map.Entry::getKey).toArray(String[]::new);
                    sb.append(parseToCsvLine(titles));
                } else {
                    // 数据
                    String[] columns = lineSet.stream().map(obj -> obj.getValue().toString()).toArray(String[]::new);
                    sb.append(parseToCsvLine(columns));
                }
                if (i < list.size() - 1) {
                    sb.append(System.lineSeparator());
                }
            }
        } else {
            sb.append(parseToCsvLine(new String[]{JsonUtils.toString(output.toString())})).append(System.lineSeparator());
        }
        outputStream.write(sb.toString().getBytes(StandardCharsets.UTF_8));
    }

    /**
     * 导出excel内容
     *
     * @param output 返回结果
     */
    public static void writeExcelContent(OutputStream outputStream, Object output, String suffix) throws IOException {
        Workbook workbook;
        if ("xls".equals(suffix)) {
            workbook = new HSSFWorkbook();
        } else {
            workbook = new XSSFWorkbook();
        }
        if (output instanceof List) {
            writeSheet(workbook, suffix, output, "sheet0");
        } else if (output instanceof Map) {
            for (Map.Entry<?, ?> entry : ((Map<?, ?>) output).entrySet()) {
                String sheetName = entry.getKey().toString();
                writeSheet(workbook, suffix, entry.getValue(), sheetName);
            }
        } else {
            writeSheet(workbook, suffix, output, "sheet0");
        }
        workbook.write(outputStream);
    }

    public static void writeSheet(Workbook workbook, String suffix, Object output, String sheetName) {
        Sheet sheet = workbook.createSheet(sheetName);
        CellStyle cellStyle = workbook.createCellStyle();
        cellStyle.setWrapText(true);
        cellStyle.setAlignment(HorizontalAlignment.CENTER);
        cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
        if (output instanceof List) {
            List<Map<String, Object>> list = (List<Map<String, Object>>) output;
            // title
            Set<Map.Entry<String, Object>> titleSet = list.get(0).entrySet();
            Row row = sheet.createRow(0);
            int column = 0;
            for (Map.Entry<String, Object> lineEntry : titleSet) {
                sheet.setColumnWidth(column, lineEntry.getKey().getBytes().length * 2 * 256);
                Cell cell = row.createCell(column++);
                cell.setCellStyle(cellStyle);
                cell.setCellValue(generateRichTextString(suffix, lineEntry.getKey()));
            }
            // table
            for (int i = 0; i < list.size(); i++) {
                column = 0;
                Set<Map.Entry<String, Object>> lineSet = list.get(i).entrySet();
                row = sheet.createRow(i + 1);
                for (Map.Entry<String, Object> lineEntry : lineSet) {
                    Cell cell = row.createCell(column++);
                    cell.setCellStyle(cellStyle);
                    cell.setCellValue(generateRichTextString(suffix, lineEntry.getValue() + ""));
                }
            }
        } else {
            Row row = sheet.createRow(0);
            Cell cell = row.createCell(0);
            cell.setCellStyle(cellStyle);
            cell.setCellValue(generateRichTextString(suffix, JsonUtils.toString(output)));
        }
    }

    private static RichTextString generateRichTextString(String suffix, String value) {
        if ("xlsx".equals(suffix)) {
            return new XSSFRichTextString(value);
        }
        return new HSSFRichTextString(value);
    }

    private static String parseToCsvLine(String[] values) {
        return Stream.of(values).map(ExportUtil::convertToCsvValue).collect(Collectors.joining(","));
    }

    private static String convertToCsvValue(String value) {
        char separator = ',';
        char quoteChar = '"';
        char escapeChar = '\\';
        value = value == null ? "" : value;
        StringBuilder builder = new StringBuilder(value.length() * 2);
        boolean containsQuoteChar = StringUtils.contains(value, quoteChar);
        boolean containsEscapeChar = StringUtils.contains(value, escapeChar);
        boolean surroundWithQuotes = StringUtils.contains(value, separator) || value.contains("\n");
        String convertedString = !containsQuoteChar ? value : value.replaceAll(Character.toString(quoteChar), Character.toString(quoteChar) + quoteChar);
        convertedString = !containsEscapeChar ? convertedString : convertedString.replace(Character.toString(escapeChar), Character.toString(escapeChar) + escapeChar);
        if (surroundWithQuotes) {
            builder.append(quoteChar);
        }
        builder.append(convertedString);
        if (surroundWithQuotes) {
            builder.append(quoteChar);
        }
        return builder.toString();
    }

    /**
     * 获取临时导出文件的根目录
     *
     * @return 临时根目录
     */
    public static String fetchExportRootPath() {
        File rootPath;
        try {
            rootPath = createTempDirectory();
        } catch (IOException e) {
            throw new AmapException(AmapExceptionCode.COMMON_SERVER_ERROR, "导出文件异常-创建临时根目录失败");
        }
        return rootPath.getAbsolutePath();
    }

    /**
     * 导出指定内容至临时目录
     *
     * @param rootPath    临时根目录
     * @param projectName 项目名称
     * @param type        类型:interface/datasource/...
     * @param fileName    导出文件名
     * @param content     导出内容
     */
    public static void export(String rootPath, String projectName, String type, String fileName, String content) {
        String pathName = rootPath + projectName + "/" + type;
        File folder = new File(pathName);
        if (!folder.exists() || !folder.isDirectory()) {
            if (!folder.mkdirs()) {
                throw new AmapException(AmapExceptionCode.COMMON_SERVER_ERROR, "导出文件异常-创建临时工作目录失败");
            }
        }

        File file = new File(pathName + "/" + fileName + ".json");
        try (OutputStream os = new FileOutputStream(file)) {
            if (!file.exists()) {
                if (!folder.createNewFile()) {
                    throw new AmapException(AmapExceptionCode.COMMON_SERVER_ERROR, "导出文件异常-创建文件失败");
                }
            }
            byte[] buffer = content.getBytes();
            os.write(buffer, 0, buffer.length);
        } catch (IOException e) {
            throw new AmapException(AmapExceptionCode.COMMON_SERVER_ERROR, "导出文件异常", e);
        }
    }

    /**
     * 压缩并触发浏览器下载文件
     *
     * @param rootPath    临时根目录
     * @param projectName 项目名称-导出文件夹名称
     * @param response    http servlet response
     */
    public static void zipNTrans(String rootPath, String projectName, HttpServletResponse response) {
        File folder = new File(rootPath + projectName);
        if (!folder.exists() || !folder.isDirectory()) {
            throw new AmapException(AmapExceptionCode.COMMON_SERVER_ERROR, "压缩文件异常-导出的文件夹不存在");
        }
        try (ZipOutputStream zos = new ZipOutputStream(response.getOutputStream())) {
            compress(folder, zos, folder.getName());
        } catch (IOException e) {
            throw new AmapException(AmapExceptionCode.COMMON_SERVER_ERROR, "压缩文件异常", e);
        }
    }

    /**
     * 遍历文件夹，压缩导出的本地化/离线方案涉及的文件
     *
     * @param source   导出的文件夹/目录
     * @param zos      压缩输出流
     * @param fileName 被压缩的文件名
     */
    private static void compress(File source, ZipOutputStream zos, String fileName) {
        if (source.isFile()) {
            try (FileInputStream fis = new FileInputStream(source)) {
                zos.putNextEntry(new ZipEntry(fileName));
                int len;
                while ((len = fis.read()) != -1) {
                    zos.write(len);
                }
                zos.closeEntry();
            } catch (IOException e) {
                throw new AmapException(AmapExceptionCode.COMMON_SERVER_ERROR, "压缩文件异常", e);
            }
        } else {
            File[] fileList = source.listFiles();
            if (fileList != null && fileList.length > 0) {
                for (File file : fileList) {
                    compress(file, zos, fileName + "/" + file.getName());
                }
            }
        }
    }

    /**
     * 清除临时导出文件的根目录
     *
     * @param rootPath 临时根目录
     */
    public static void clearFileByPath(String rootPath) {
        File source = new File(rootPath);
        if (source.isFile()) {
            if (!source.delete()) {
                throw new AmapException(AmapExceptionCode.COMMON_SERVER_ERROR, "清除文件异常" + source.getAbsolutePath());
            }
        } else {
            File[] fileList = source.listFiles();
            if (fileList != null && fileList.length > 0) {
                for (File file : fileList) {
                    clearFileByPath(file.getAbsolutePath());
                }
            }
            if (!source.delete()) {
                throw new AmapException(AmapExceptionCode.COMMON_SERVER_ERROR, "清除文件夹异常" + source.getAbsolutePath());
            }
        }
    }

    /**
     * 获取文件夹信息
     *
     * @param rootPath 临时根目录
     * @return 文件夹信息
     */
    public static String fetchFolderInfoByPath(String rootPath) {
        File file = new File(rootPath);
        if (file.exists()) {
            try {
                return JsonUtils.toString(file.list());
            } catch (Exception e) {
                throw new AmapException(AmapExceptionCode.COMMON_SERVER_ERROR, "获取文件夹信息异常", e);
            }
        }
        return null;
    }
}
